package pyip.lib.javalearn.thread.lock.waitnotify;

import pyip.lib.javalearn.Printer;

/**
 * 在拥有某个对象 obj的锁情况下, 调用 #obj.wait将会释放 obj锁, 其他线程就可以进入了.<br>
 * <br>
 * 如果调用 #obj.wait时, 没有获得obj锁, 将会抛出 IllegalMonitorStateException(if the current thread is not the owner of the object's monitor.)<br>
 * <br>
 * 为什么在执行wait, notify时，必须获得该对象的锁？<br>
 * 这是因为，如果没有锁，wait和notify有可能会产生竞态条件(Race Condition)。考虑以下生产者和消费者的情景：<br>
 * 1.1生产者检查条件（如缓存满了）-> 1.2生产者必须等待<br>
 * 2.1消费者消费了一个单位的缓存 -> 2.2重新设置了条件（如缓存没满） -> 2.3调用notifyAll()唤醒生产者<br>
 * 我们希望的顺序是： 1.1->1.2->2.1->2.2->2.3<br>
 * 但在多线程情况下，顺序有可能是 1.1->2.1->2.2->2.3->1.2。也就是说，在生产者还没wait之前，消费者就已经notifyAll了，这样的话，生产者会一直等下去。<br>
 * 所以，要解决这个问题，必须在wait和notifyAll的时候，获得该对象的锁，以保证同步。
 * @author yepeng
 * @date 2015年4月23日
 */
public class WaitReleaseMonitorDemo implements Runnable {

    public static void main(String[] args) {

        Printer.out.println("main start", 0);

        WaitReleaseMonitorDemo run = new WaitReleaseMonitorDemo();
        Thread t1 = new Thread(run, "t1");
        Thread t2 = new Thread(run, "t2");
        t1.start();
        t2.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Printer.out.println("main interrupt/notify t1 t2", 0);

        // 退出线程
        // t1.interrupt();
        // t2.interrupt();
        synchronized (run) {
            run.notifyAll();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Printer.out.println("main exit", 0);
    }

    private boolean sleepAtFirstTime = true;

    public synchronized void run() {
        String name = Thread.currentThread().getName();
        Printer.out.println(name + " start", 1);

        try {
            if (sleepAtFirstTime) {
                Thread.sleep(3000);
                sleepAtFirstTime = false;
                Printer.out.println(name + " business", 1);
            }

            Printer.out.println(name + " wait", 1);
            // 等待, 并放弃了 this锁, 其他线程可以进来了
            this.wait();
        } catch (InterruptedException e) {
            // exit
            Printer.out.println(name + " be interrupted", 1);
        }
        Printer.out.println(name + " exit", 1);
    }
}